/*! \file HXDisplay.h
    \brief Head X model rendering engine.

	Head X | Flinders University | Martin Luerssen & Trent Lewis | 2009+
*/

#pragma once

class HXDisplay;

#include "HXAlive.h"
#include "HXConfig.h"
#include "HXPlayer.h"
#include "HXRender.h"

// OpenGL Header Files:
#define GLEW_STATIC
#include "GL/glew.h"
#include "GL/wglew.h"
#include "GL/glu.h"

class FTFont; // prototype - to avoid including GL headers in this header

//-----------------------------------------------------------------------------

void SetVSync(bool bSync);

//-----------------------------------------------------------------------------

//! Specify different "sources" of animations.
enum HXMorphStream
{
	HXMS_OTHER = 0,
	HXMS_EMOTE = 1,
	HXMS_IDLE = 2,
	HXMS_SPEECH = 3
};

struct HXMorphData
{
	HXMorphData() : other(0), emote(0), idle(0), speech(0) {}
	float GetAmplitude();

	float other;
	float emote;
	float idle;
	float speech;
	list<float*> pointerCache;
};

//-----------------------------------------------------------------------------

#define SUSPENDPAINT Lock(); bool __suspendPrior = m_bSuspendPaint; m_bSuspendPaint = true
#define UNSUSPENDPAINT m_bSuspendPaint = __suspendPrior; Unlock()

//! 3D drawing code alternatives.
enum HXDrawMode
{
	HXDRAW_LEGACY,
	HXDRAW_SHADERS,
	HXDRAW_DEPTH_INIT,
	HXDRAW_DEPTH_PEEL,
	HXDRAW_FRONT_INIT,
	HXDRAW_FRONT_PEEL
};

//! Generic wrapper around a device-independent bitmap.
struct HXBufferImage
{
	~HXBufferImage() { Destroy(); }
	bool Create(int _width, int _height);
	void Destroy();
	void PreMultAlpha();
	size_t CountFilled(bool useTemp);

    int width;
    int height;
    int pitch;
    HDC hdc;
    HBITMAP hBitmap;
    BITMAPINFO info;
    BYTE *pPixels;
	BYTE *pTempPixels;
};

//! 3D model rendering window.
class HXDisplay
{
public:
	HXDisplay();
	virtual ~HXDisplay();

	bool	LockNow();
	void	Lock();
	void	Unlock();
	bool	IsLocked() { return m_lockCount > 0; }
	bool	IsSuspended() { return m_bSuspendPaint; }

	virtual size_t	LoadFromConfig(HXPlayerConfig& config);
	virtual size_t	LoadModelGroup(HXResourceGroup& group);
	bool	UnloadSingleModel(size_t idx, HXResourceGroup& group);
	bool	UnloadModelGroup(wstring& groupName, HXResourceGroup& group);
	void	UnloadAllModels(HXResourceGroup& group);
	HXRenderData*	GetModel(size_t idx);

	bool	LoadModel(HXResource& res);
	bool	LoadHxModel(const wstring& modelFile, size_t item, float shininess, float alpha_threshold, bool center, int recomp_normal);
	bool	LoadObjModel(const HXResource& res);
	size_t	ModelSize() const { return m_renderList.size(); }
	bool	ExportModels(const wstring& filePath);
	
	bool	LoadFont(const wstring& fontFile, unsigned int size);
	void	UnloadFont();
	bool	InitFont(unsigned int size);
	bool	LoadBackground(const wstring& bmpFile);
	void	UnloadBackground();
	void	UpdateBackground();
	void	BuildBackground();
	bool	CreateDisplay(HWND hwnd, bool bLayered);
	bool	SwitchDisplay(HWND hwnd, bool bLayered);
	void	ResetDisplay();
	void	ChangeLight(size_t lightId, const HXLight& light);
	void	ChangeLight(size_t lightId, float azimuth, float elevation, float ambient, float diffuse);
	void	ResetLights(bool loadFromConfig);
	void	BuildNewRenderData();
	void	BuildVertexData();
	void	UpdateTextureData(bool bMipmap);
	void	BuildTextureData(bool bMipmap = true);
	void	BuildShaders();
	bool	IsLayeredDisplay() { return m_pImage ? true : false; }
	void	Paint();
	void	RedrawLayeredWindow();
	int		GetWidth() { return m_width; }
	int		GetHeight() { return m_height; }
	void	SetSize(int cx, int cy);
	void	Rotate(HXResourceGroup& rg, float delX, float delY, float delZ);
	void	ResetRotation(HXResourceGroup& rg);
	void	Scale(HXResourceGroup& rg, float delX, float delY, float delZ);
	void	ResetScale(HXResourceGroup& rg);
	void	Translate(HXResourceGroup& rg, float delX, float delY, float delZ);
	void	ResetTranslation(HXResourceGroup& rg);
	void	MoveSubtitlePosition(float x = 0.0f, float y = 0.0f, float z = 0.0f);
	void	ResetSubtitlePosition();
	void	ReadCameraLights(float* pBuffer);
	void	ChangeCameraLights(float* pBuffer);

	void	CameraStatus();
	void	LightStatus();
	void	SubtitleStatus();

	size_t	MorphCount() { return m_morphData.size(); }
	HXMorph* GetMorph(size_t idx);
	void	GetMorphLabels(vector<wstring>& labelArray); 
	void	ResetAllMorphs();
	void	CreateMorphCache();
	void	ClearMorphCache();
	void	ResetMorphValues(HXMorphStream driver);
	void	SetMorphValue(HXMorphStream driver, const wstring& label, float value);
	void	ChangeMorphValue(HXMorphStream driver, const wstring& label, float value);
	void	GetRemoteMorphs(float* pBuffer, size_t max_size);
	void	ChangeRemoteMorphs(float* pBuffer, size_t max_size);
	void	ResetRemoteMorphs();
	void	UpdateMorphs();
	void	SetAlive(HXAliveProcessor* pAlive) { m_pAlive = pAlive; }
	HXAliveProcessor*	GetAlive() { return m_pAlive; }

	void	DestroyDisplay(bool bSubstantive = true);

protected:
	bool	UnloadModel(size_t idx, HXResourceGroup& group);
	bool	InitDepthPeeling();
	bool	InitFrontPeelingShaders();
	bool	InitDualPeelingShaders();
	bool	InitOtherShaders();
	void	InitMainFBO();
	void	InitDualPeelingRenderTargets();
	void	InitFrontPeelingRenderTargets();
	void	MakeFullScreenQuad(GLuint& quad);
	void	DeleteDepthPeeling();
	void	DeleteFrontPeelingShaders();
	void	DeleteDualPeelingShaders();
	void	DeleteOtherShaders();
	void	DeleteMainFBO();
	void	DeleteFrontPeelingRenderTargets();
	void	DeleteDualPeelingRenderTargets();
	void	ReshapeRenderTargets();
	void	DrawLegacy();
	void	DrawWithShaders();
	void	DrawWithDepth();
	void	DrawBackground(HXShader* pShader);
	void	StartProjection();
	void	RecursiveDraw(HXResourceGroup& rg, HXDrawMode drawMode);
	
	list<HXRenderData> m_renderList; // models
	map<wstring, HXMorphData> m_morphData;

	HDC             m_hdc;
	HGLRC           m_hglrc;
	HWND			m_hwnd;
	HXBufferImage*	m_pImage;
	HXVector3F      m_aveVal;
	float           m_dimMax;
	float			m_nearZ, m_farZ;
	HXVector3F      m_trans, m_rot;
	HXMatrix3F		m_lightPose[2];
	HXLight			m_light[2];
	int				m_width, m_height;
	FTFont*			m_pFont;
	unsigned char*	m_pFontBuffer;
	size_t			m_pFontBufferSize;
	HXImageRGBA		m_background;
	unsigned int	m_oglTexName;
	
	HXPlayerConfig*		m_pConfig;
	HXAliveProcessor*	m_pAlive;
	void* m_UpdatePriorityObject;

	// Thread sync
	HANDLE			m_hMutex;
	unsigned int	m_lockCount;
	bool			m_bSuspendPaint;
	bool			m_bOpenGL2Supported;
	// for transparent backgrounds and...
	bool m_bFBOArbSupported, m_bFBOExtSupported;
	// for depth peeling/shaders
	bool m_bDepthPeelingSupported;

	HXShader m_shaderDualInit;
	HXShader m_shaderDualPeel;
	HXShader m_shaderDualBlend;
	HXShader m_shaderDualFinal;
	HXShader m_shaderPhong;
	HXShader m_shaderSimple;

	HXShader m_shaderFrontInit;
	HXShader m_shaderFrontPeel;
	HXShader m_shaderFrontBlend;
	HXShader m_shaderFrontFinal;

	GLuint m_mainFboId;
	GLuint m_mainColorTexId;
	GLuint m_mainDepthTexId;
	GLuint m_dualBackBlenderFboId;
	GLuint m_dualPeelingSingleFboId;
	GLuint m_dualDepthTexId[2];
	GLuint m_dualFrontBlenderTexId[2];
	GLuint m_dualBackTempTexId[2];
	GLuint m_dualBackBlenderTexId;
	GLuint m_quadDisplayList;
	GLuint m_queryId;

	GLuint m_frontFboId[2];
	GLuint m_frontDepthTexId[2];
	GLuint m_frontColorTexId[2];
	GLuint m_frontColorBlenderTexId;
	GLuint m_frontColorBlenderFboId;

	GLenum m_drawBuffers[7];

	float m_opacity;
	int m_imageWidth;
	int m_imageHeight;
};

//-----------------------------------------------------------------------------

